package com.app.motion.compiler;

import com.app.motion.annotations.BindView;
import com.app.motion.annotations.OnClick;
import com.squareup.javapoet.*;

import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;


public class BasicProcessor extends AbstractProcessor {

    private Filer mFiler;
    private Messager mMessage;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        mMessage = processingEnv.getMessager();
        mElementUtils = processingEnv.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return new TreeSet<>(Arrays.asList(BindView.class.getCanonicalName(),
                OnClick.class.getCanonicalName()));
    }


    private Set<? extends TypeElement> getTypeElementByAnnotationType(Set<? extends TypeElement> annotations,
                                                                      Set<? extends Element> elements) {
        Set<TypeElement> result = new HashSet<>();
        for (Element element : elements) {
            if (element instanceof TypeElement) {
                label:
                for (Element subElement : element.getEnclosedElements()) {
                    for (AnnotationMirror mirror : subElement.getAnnotationMirrors()) {
                        for (TypeElement currentAnnotation : annotations) {
                            if (mirror.getAnnotationType().asElement().equals(currentAnnotation)) {
                                result.add((TypeElement) element);
                                break label;
                            }
                        }
                    }
                }
            }

        }
        return result;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends TypeElement> result = getTypeElementByAnnotationType(annotations, roundEnv.getRootElements());
        for (TypeElement element : result) {
            String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
            String simpleName = element.getSimpleName().toString();
            ClassName className = ClassName.get(packageName, simpleName);
            ClassName genertorClassName = ClassName.get(packageName, simpleName + "$Binding");
            TypeSpec.Builder builder = TypeSpec.classBuilder(genertorClassName)
                    .addModifiers(Modifier.PUBLIC);

            builder.addMethod(MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(className, "activity", Modifier.FINAL)
                    .addStatement("$N($N)", "bindView", "activity")
                    .addStatement("$N($N)", "setOnClickListener", "activity")
                    .build());

            MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PRIVATE)
                    .returns(TypeName.VOID)
                    .addParameter(className, "activity", Modifier.FINAL);

            for (VariableElement variableElement : ElementFilter.fieldsIn(element.getEnclosedElements())) {
                BindView bindView = variableElement.getAnnotation(BindView.class);
                if (bindView != null) {
                    bindMethodBuilder.addStatement("$N.$N=($T)$N.findViewById($L)",
                            "activity", variableElement.getSimpleName(), variableElement, "activity",
                            bindView.values()[0])
                            .addStatement("$N.$N.setText($N.getString($L))",
                                    "activity",
                                    variableElement.getSimpleName(),
                                    "activity",
                                    bindView.values()[1]);

                }
            }

            builder.addMethod(bindMethodBuilder.build());


            MethodSpec.Builder onClickMethodBuild = MethodSpec.methodBuilder("setOnClickListener")
                    .addModifiers(Modifier.PRIVATE)
                    .returns(TypeName.VOID)
                    .addParameter(className, "activity", Modifier.FINAL);

            ClassName viewName = className.get("android.view", "View");
            ClassName onClickClassName = className.get("android.view", "View", "OnClickListener");
            for (ExecutableElement executableElement : ElementFilter.methodsIn(element.getEnclosedElements())) {
                OnClick onClick = executableElement.getAnnotation(OnClick.class);
                if (onClick != null) {
                    TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("")
                            .addSuperinterface(onClickClassName)
                            .addMethod(MethodSpec.methodBuilder("onClick")
                                    .addModifiers(Modifier.PUBLIC)
                                    .returns(TypeName.VOID)
                                    .addParameter(viewName, "view")
                                    .addStatement("$N.$N($N)", "activity", executableElement.getSimpleName(), "view")
                                    .build())
                            .build();

                    onClickMethodBuild.addStatement("$N.findViewById($L).setOnClickListener($L)", "activity",
                            onClick.values()[0], typeSpec);
                }
            }
            builder.addMethod(onClickMethodBuild.build());

            try {
                JavaFile.builder(packageName, builder.build()).build().writeTo(mFiler);
            } catch (IOException e) {
                mMessage.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), element);
            }
        }
        return true;
    }


}
